From c58ae69360ccf2495a19bf4ca107e21cf873c75b Mon Sep 17 00:00:00 2001 From: Jeremy Fitzhardinge Date: Thu, 24 May 2007 10:47:27 +0100 Subject: [PATCH] Implement VCPUOP_register_vcpu_info This change implements the VCPUOP_register_vcpu_info vcpu_op. This allows a guest to choose where each VCPU's vcpu_info structure is placed within its address space, allowing it to put it somewhere which is easily accessible via some per-cpu data access mechanism. When changing the mapping of the vcpu info, there's no obvious way to prevent the other vcpus from getting a stale pointer of the vcpu_info, which could result in them accessing bad memory (stale pointers to the shared_info page are not a problem, because its always valid). To avoid this, we prevent guests from changing the vcpu_info location more than once, since there's no obvious need to allow them to do this at this point. (If we really want to allow guests to update the vcpu_info location more than once, then some sort of RCU wait between updating the pointer and performing the unmap may be the way to do it.) Signed-off-by: Jeremy Fitzhardinge --- xen/arch/x86/domain.c | 102 ++++++++++++++++++++++++++++++++++++++ xen/common/domain.c | 1 + xen/include/public/vcpu.h | 3 +- xen/include/xen/sched.h | 1 + 4 files changed, 105 insertions(+), 2 deletions(-) diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c index 121c484d47..e413b0249c 100644 --- a/xen/arch/x86/domain.c +++ b/xen/arch/x86/domain.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,8 @@ DEFINE_PER_CPU(struct vcpu *, curr_vcpu); DEFINE_PER_CPU(__u64, efer); +static void unmap_vcpu_info(struct vcpu *v); + static void paravirt_ctxt_switch_from(struct vcpu *v); static void paravirt_ctxt_switch_to(struct vcpu *v); @@ -728,11 +731,94 @@ int arch_set_info_guest( int arch_vcpu_reset(struct vcpu *v) { + unmap_vcpu_info(v); destroy_gdt(v); vcpu_destroy_pagetables(v); return 0; } +/* + * Unmap the vcpu info page if the guest decided to place it somewhere + * else. This is only used from arch_vcpu_reset, so there's no need + * to do anything clever. + */ +static void +unmap_vcpu_info(struct vcpu *v) +{ + struct domain *d = v->domain; + unsigned long mfn; + + if ( v->vcpu_info_mfn == INVALID_MFN ) + return; + + mfn = v->vcpu_info_mfn; + unmap_domain_page_global( v->vcpu_info ); + + v->vcpu_info = shared_info_addr(d, vcpu_info[v->vcpu_id]); + v->vcpu_info_mfn = INVALID_MFN; + + put_page_and_type(mfn_to_page(mfn)); +} + +/* + * Map a guest page in and point the vcpu_info pointer at it. This + * makes sure that the vcpu_info is always pointing at a valid piece + * of memory, and it sets a pending event to make sure that a pending + * event doesn't get missed. + */ +static int +map_vcpu_info(struct vcpu *v, unsigned long mfn, unsigned offset) +{ + struct domain *d = v->domain; + void *mapping; + vcpu_info_t *new_info; + int i; + + if ( offset > (PAGE_SIZE - sizeof(vcpu_info_t)) ) + return -EINVAL; + + if ( mfn == INVALID_MFN || + v->vcpu_info_mfn != INVALID_MFN ) + return -EINVAL; + + mfn = gmfn_to_mfn(d, mfn); + if ( !mfn_valid(mfn) || + !get_page_and_type(mfn_to_page(mfn), d, PGT_writable_page) ) + return -EINVAL; + + mapping = map_domain_page_global(mfn); + if ( mapping == NULL ) + { + put_page_and_type(mfn_to_page(mfn)); + return -ENOMEM; + } + + new_info = (vcpu_info_t *)(mapping + offset); + + memcpy(new_info, v->vcpu_info, sizeof(*new_info)); + + v->vcpu_info = new_info; + v->vcpu_info_mfn = mfn; + + /* make sure all the pointers are uptodate before setting pending */ + wmb(); + + /* Mark everything as being pending just to make sure nothing gets + lost. The domain will get a spurious event, but it can + cope. */ + vcpu_info(v, evtchn_upcall_pending) = 1; + for ( i = 0; i < BITS_PER_GUEST_LONG(d); i++ ) + set_bit(i, vcpu_info_addr(v, evtchn_pending_sel)); + + /* Only bother to update time for the current vcpu. If we're + * operating on another vcpu, then it had better not be running at + * the time. */ + if ( v == current ) + update_vcpu_system_time(v); + + return 0; +} + long arch_do_vcpu_op( int cmd, struct vcpu *v, XEN_GUEST_HANDLE(void) arg) @@ -769,6 +855,22 @@ arch_do_vcpu_op( break; } + case VCPUOP_register_vcpu_info: + { + struct domain *d = v->domain; + struct vcpu_register_vcpu_info info; + + rc = -EFAULT; + if ( copy_from_guest(&info, arg, 1) ) + break; + + LOCK_BIGLOCK(d); + rc = map_vcpu_info(v, info.mfn, info.offset); + UNLOCK_BIGLOCK(d); + + break; + } + default: rc = -ENOSYS; break; diff --git a/xen/common/domain.c b/xen/common/domain.c index 5d13042f73..031ee3fc8c 100644 --- a/xen/common/domain.c +++ b/xen/common/domain.c @@ -136,6 +136,7 @@ struct vcpu *alloc_vcpu( v->domain = d; v->vcpu_id = vcpu_id; + v->vcpu_info_mfn = INVALID_MFN; v->runstate.state = is_idle_vcpu(v) ? RUNSTATE_running : RUNSTATE_offline; v->runstate.state_entry_time = NOW(); diff --git a/xen/include/public/vcpu.h b/xen/include/public/vcpu.h index 845f7e2c05..6c6558e3ca 100644 --- a/xen/include/public/vcpu.h +++ b/xen/include/public/vcpu.h @@ -168,8 +168,7 @@ DEFINE_XEN_GUEST_HANDLE(vcpu_set_singleshot_timer_t); * The pointer need not be page aligned, but the structure must not * cross a page boundary. * - * If the specified mfn is INVALID_MFN, then it reverts to using the - * vcpu_info structure in the shared_info page. + * This may be called only once per vcpu. */ #define VCPUOP_register_vcpu_info 10 /* arg == struct vcpu_info */ struct vcpu_register_vcpu_info { diff --git a/xen/include/xen/sched.h b/xen/include/xen/sched.h index 703b339918..5ce2f5db7a 100644 --- a/xen/include/xen/sched.h +++ b/xen/include/xen/sched.h @@ -75,6 +75,7 @@ struct vcpu int processor; vcpu_info_t *vcpu_info; + unsigned long vcpu_info_mfn; struct domain *domain; -- 2.30.2